package com.osdp.gateway.predicate;

import com.alibaba.fastjson.JSONObject;
import com.osdp.gateway.service.ITokenService;
import com.osdp.gateway.util.CommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;

import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

/**
 * DynamicRouteConfig中配置为bean
 * Predicate(谓语) 路由断言
 * gateway:
 *       routes:
 *         #自定义谓词TokenCheckRoutePredicateFactory的配置使用
 *         #谓词用来匹配路径
 *         -  id: Auth_route
 *            uri: lb://mima-cloud-producer
 *            order: 1
 *            predicates:
 *              - Path=/**
 *              # name配置为TokenCheckRoutePredicateFactory类前缀TokenCheck
 *              # 忽略的url地址{/auth/login/**,/login/**}
 *              - name: TokenCheck
 *                args:
 *                  ignores: /auth/login/**,/login/**
 */
@Slf4j
public class TokenCheckRoutePredicateFactory extends
        AbstractRoutePredicateFactory<TokenCheckRoutePredicateFactory.Conifg>
        implements ApplicationContextAware {

    public static final String IGNORES_KEY = "ignores";
    private static final AntPathMatcher matcher = new AntPathMatcher();
    private ApplicationContext applicationContext;

    public TokenCheckRoutePredicateFactory() {
        super(TokenCheckRoutePredicateFactory.Conifg.class);
    }

    /**
     * 没有token 不允许
     * @param request
     * @return
     */
    private boolean allow(ServerHttpRequest request){
        String token = request.getHeaders().getFirst(CommonConstant.HEADER_ACCESS_TOKEN);
        if(StringUtils.isEmpty(token)){
            if(log.isDebugEnabled()) {log.debug("无法从请求头X-Access-Token获取到token值");}
            return false;
        }
        // 验证token
        return applicationContext.getBean(ITokenService.class).checkToken(token);
    }

    /**
     * path相等就忽略(ignore)
     * @param ignores
     * @param path
     * @return
     */
    private boolean ignore(List<String> ignores, String path){
        if(CollectionUtils.isEmpty(ignores)){
            return false;
        }
        for(String ignore : ignores){
            if(matcher.match(ignore, path)) {
                return true;
            }
        }
        return false;
    }

    /**
     * singletonList返回的是不可变的集合，但是这个长度的集合只有1，可以减少内存空间。
     * 但是返回的值依然是Collections的内部实现类，同样没有add的方法，调用add，set方法会报错
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder(){
        return Collections.singletonList(IGNORES_KEY);
    }

    /**
     * 申请
     * @param config
     * @return
     */
    @Override
    public Predicate<ServerWebExchange> apply(Conifg config) {
        List<String> ignores = JSONObject.parseArray(config.getIgnores(),String.class);

        return exchange -> {
            // uri = new URI("https://www.geeksforgeeks.org/url-getprotocol/");
            //Raw Path=/url-getprotocol/
            String path = exchange.getRequest().getURI().getRawPath();
            System.out.println("----------path: "+ path);
            boolean allowed = ignore(ignores, path) ? true : allow(exchange.getRequest()) ;
            if(!allowed){
                if(log.isDebugEnabled()){ log.info("请求【{}】,token校验失败",allowed);}
            }
            return allowed;
        };
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public static class Conifg{
        private String ignores="";

        public String getIgnores() {
            return ignores;
        }

        public Conifg setIgnores(String ignores) {
            this.ignores = ignores;
            return this;
        }
    }
}
